﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="ContextConfigurationFactory.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Provides assembly-level static configuration and caching
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Provides assembly-level static configuration and caching
    /// </summary>
    internal static class ContextConfigurationFactory
    {
        /// <summary>
        /// Key for accessing PropertyInfo
        /// </summary>
        internal const string PropertyInfoKey = "PropertyInfoKey";

        /// <summary>
        /// Key for accessing a DefaultInstance
        /// </summary>
        internal const string DefaultInstanceKey = "DefaultInstanceKey";

        /// <summary>
        /// Instances cache
        /// </summary>
        private static readonly Dictionary<Type, object> instances = new Dictionary<Type, object>();

        /// <summary>
        /// Named Instances cache
        /// </summary>
        private static readonly Dictionary<Type, Dictionary<string, object>> namedInstances = new Dictionary<Type, Dictionary<string, object>>();

        /// <summary>
        /// Gets an instance of a specified plugin type
        /// </summary>
        /// <param name="pluginType">The plugin type for which to retrieve an instance</param>
        /// <returns>An instance of the plugin type</returns>
        public static Type GetInstance(Type pluginType)
        {
            if (!instances.ContainsKey(pluginType))
            {
                return null;
            }

            return instances[pluginType] as Type;
        }

        /// <summary>
        /// Gets an instance of the specified type
        /// </summary>
        /// <typeparam name="T">The type for which an instance should be retrieved</typeparam>
        /// <returns>The retrieved instance</returns>
        public static T GetInstance<T>()
        {
            if (!instances.ContainsKey(typeof(T)))
            {
                return default(T);
            }

            var obj = instances[typeof(T)];

            return !ReferenceEquals(obj, default(T)) && obj is T
                       ? (T)obj
                       : default(T);
        }

        /// <summary>
        /// Gets a specificly-named instance of the specified type
        /// </summary>
        /// <typeparam name="T">The type for which an instance should be retrieved</typeparam>
        /// <param name="name">The named instance that should be retrieved</param>
        /// <returns>The retrieved instance</returns>
        public static T GetInstance<T>(string name)
        {
            if (string.IsNullOrEmpty(name) || !namedInstances.ContainsKey(typeof(T)))
            {
                return GetInstance<T>();
            }

            if (!namedInstances[typeof(T)].ContainsKey(name))
            {
                return GetInstance<T>();
            }

            var obj = namedInstances[typeof(T)][name];

            return obj is T
                       ? (T)obj
                       : default(T);
        }

        /// <summary>
        /// Returns the named instance of the specified type
        /// </summary>
        /// <param name="pluginType">The plugin type for which an instance should be retrieved</param>
        /// <param name="name">The named instance that should be retrieved</param>
        /// <returns>The retrieved instance</returns>
        public static Type GetInstance(Type pluginType, string name)
        {
            if (string.IsNullOrEmpty(name) || !namedInstances.ContainsKey(pluginType))
            {
                return GetInstance(pluginType);
            }

            if (!namedInstances[pluginType].ContainsKey(name))
            {
                return GetInstance(pluginType);
            }

            return namedInstances[pluginType][name] as Type;
        }

        /// <summary>
        /// Starts a new context configuration instance
        /// </summary>
        /// <typeparam name="T">The type of the expression</typeparam>
        /// <returns>A new context configuration instance expression</returns>
        public static ContextConfigurationInstanceExpression<T> For<T>()
        {
            return new ContextConfigurationInstanceExpression<T>();
        }

        /// <summary>
        /// Starts a new context configuration instance
        /// </summary>
        /// <param name="type">The type of the expression</param>
        /// <returns>A new context configuration instance expression</returns>
        public static UntypedContextConfigurationInstanceExpression For(Type type)
        {
            var exp = new UntypedContextConfigurationInstanceExpression();
            exp.PluginType = type;
            return exp;
        }

        /// <summary>
        /// Creates a named instance context configuration instance expression
        /// </summary>
        /// <typeparam name="T">The type of object</typeparam>
        /// <param name="x">The original instance expression</param>
        /// <param name="name">The name of the instance</param>
        /// <returns>The instance</returns>
        public static NamedContextConfigurationInstanceExpression<T> Named<T>(this ContextConfigurationInstanceExpression<T> x, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("The instance can not be null or empty", "name");
            }

            return new NamedContextConfigurationInstanceExpression<T> { InstanceExpression = x, Name = name };
        }

        /// <summary>
        /// Creates a new untyped named configuration expression
        /// </summary>
        /// <param name="x">The original configuration expression</param>
        /// <param name="name">The name of the instance</param>
        /// <returns>The configuration expression</returns>
        public static UntypedNamedContextConfigurationInstanceExpression Named(this UntypedContextConfigurationInstanceExpression x, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("The instance can not be null or empty", "name");
            }

            var exp = new UntypedNamedContextConfigurationInstanceExpression();
            exp.Name = name;
            exp.PluginType = x.PluginType;
            return exp;
        }

        /// <summary>
        /// Creates a new named instance expression
        /// </summary>
        /// <typeparam name="T">The type of object</typeparam>
        /// <param name="x">The original expression</param>
        /// <param name="instance">The instance to use</param>
        public static void Use<T>(this NamedContextConfigurationInstanceExpression<T> x, T instance)
        {
            if (ReferenceEquals(instance, default(T)))
            {
                throw new ArgumentException("The instance can not be null", "instance");
            }

            if (string.IsNullOrEmpty(x.Name))
            {
                if (!instances.ContainsKey(typeof(T)))
                {
                    instances.Add(typeof(T), instance);
                }
                else
                {
                    instances[typeof(T)] = instance;
                }
            }
            else
            {
                if (!namedInstances.ContainsKey(typeof(T)))
                {
                    namedInstances.Add(typeof(T), new Dictionary<string, object>());
                }

                if (!namedInstances[typeof(T)].ContainsKey(x.Name))
                {
                    namedInstances[typeof(T)].Add(x.Name, instance);
                }
                else
                {
                    namedInstances[typeof(T)][x.Name] = instance;
                }
            }
        }

        /// <summary>
        /// Creates a new instance configuration expression
        /// </summary>
        /// <param name="x">The original configuration expression</param>
        /// <param name="pluggableType">The instance to use</param>
        public static void Use(this UntypedNamedContextConfigurationInstanceExpression x, Type pluggableType)
        {
            if (!x.PluginType.IsAssignableFrom(pluggableType))
            {
                throw new ArgumentException("The pluggable type must be assignable to the plugin type");
            }

            if (string.IsNullOrEmpty(x.Name))
            {
                if (!instances.ContainsKey(x.PluginType))
                {
                    instances.Add(x.PluginType, pluggableType);
                }
                else
                {
                    instances[x.PluginType] = pluggableType;
                }
            }
            else
            {
                if (!namedInstances.ContainsKey(x.PluginType))
                {
                    namedInstances.Add(x.PluginType, new Dictionary<string, object>());
                }

                if (!namedInstances[x.PluginType].ContainsKey(x.Name))
                {
                    namedInstances[x.PluginType].Add(x.Name, pluggableType);
                }
                else
                {
                    namedInstances[x.PluginType][x.Name] = pluggableType;
                }
            }
        }
    }
}
